多对多关系比一对多复杂,需要中间表来连接。学生和课程、文章和标签、用户和角色,都是典型的多对多关系。
用户和角色的多对多:
type User struct {
ID uint
Name string
Roles []Role `gorm:"many2many:user_roles;"`
}
type Role struct {
ID uint
Name string
Users []User `gorm:"many2many:user_roles;"`
}
GORM 会自动创建 user_roles 中间表:
CREATE TABLE user_roles (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id)
)
默认中间表名是两个表名的组合,按字母顺序排列。
自定义中间表名:
type User struct {
ID uint
Name string
Roles []Role `gorm:"many2many:sys_user_role;"`
}
中间表的外键名:
type User struct {
ID uint
Name string
Roles []Role `gorm:"many2many:user_roles;foreignKey:ID;joinForeignKey:UserID"`
}
type Role struct {
ID uint
Name string
Users []User `gorm:"many2many:user_roles;foreignKey:ID;joinForeignKey:RoleID"`
}
foreignKey - 本表的外键字段joinForeignKey - 中间表中指向本表的外键名References - 关联表的外键字段joinReferences - 中间表中指向关联表的外键名创建用户时分配角色:
user := User{
Name: "张三",
Roles: []Role{
{Name: "admin"},
{Name: "editor"},
},
}
db.Create(&user)
用户、角色、中间表记录都会创建。
如果角色已存在:
var roles []Role
db.Where("name IN ?", []string{"admin", "editor"}).Find(&roles)
user := User{
Name: "张三",
Roles: roles,
}
db.Create(&user)
var user User
db.Preload("Roles").First(&user, 1)
fmt.Println(user.Roles)
嵌套预加载:
type Role struct {
ID uint
Name string
Users []User `gorm:"many2many:user_roles;"`
Permissions []Permission `gorm:"many2many:role_permissions;"`
}
db.Preload("Roles.Permissions").First(&user, 1)
给用户添加角色:
var user User
db.First(&user, 1)
var role Role
db.First(&role, 1)
db.Model(&user).Association("Roles").Append(&role)
批量添加:
var roles []Role
db.Where("name IN ?", []string{"viewer", "guest"}).Find(&roles)
db.Model(&user).Association("Roles").Append(roles)
替换用户的所有角色:
var roles []Role
db.Where("name IN ?", []string{"admin", "super"}).Find(&roles)
db.Model(&user).Association("Roles").Replace(roles)
删除用户的某个角色:
db.Model(&user).Association("Roles").Delete(&role)
只删除中间表记录,不删除角色本身。
清空用户的所有角色:
db.Model(&user).Association("Roles").Clear()
count := db.Model(&user).Association("Roles").Count()
中间表有时需要额外字段,比如创建时间:
type User struct {
ID uint
Name string
Roles []Role `gorm:"many2many:user_roles;"`
}
type Role struct {
ID uint
Name string
Users []User `gorm:"many2many:user_roles;"`
}
type UserRole struct {
UserID uint
RoleID uint
CreatedAt time.Time
CreatedBy uint
}
手动操作中间表:
userRole := UserRole{
UserID: 1,
RoleID: 2,
CreatedAt: time.Now(),
CreatedBy: 1,
}
db.Create(&userRole)
用户关注用户:
type User struct {
ID uint
Name string
Following []User `gorm:"many2many:user_follows;foreignKey:ID;joinForeignKey:FollowerID;References:ID;joinReferences:FollowingID"`
Followers []User `gorm:"many2many:user_follows;foreignKey:ID;joinForeignKey:FollowingID;References:ID;joinReferences:FollowerID"`
}
查询用户的关注列表:
var user User
db.Preload("Following").First(&user, 1)
查询用户的粉丝:
db.Preload("Followers").First(&user, 1)
文章与标签
type Article struct {
ID uint
Title string
Tags []Tag `gorm:"many2many:article_tags;"`
}
type Tag struct {
ID uint
Name string
Articles []Article `gorm:"many2many:article_tags;"`
}
var article Article
db.Preload("Tags").First(&article, 1)
学生与课程
type Student struct {
ID uint
Name string
Courses []Course `gorm:"many2many:student_courses;"`
}
type Course struct {
ID uint
Name string
Students []Student `gorm:"many2many:student_courses;"`
}
商品与分类
商品可以属于多个分类,分类下有多个商品:
type Product struct {
ID uint
Name string
Categories []Category `gorm:"many2many:product_categories;"`
}
type Category struct {
ID uint
Name string
Products []Product `gorm:"many2many:product_categories;"`
}
中间表不存在
GORM 会自动创建中间表,但需要执行 AutoMigrate:
db.AutoMigrate(&User{}, &Role{})
关联重复添加
同一个角色添加两次,中间表会有两条记录吗?GORM 会去重,不会重复插入。
删除时中间表
删除用户或角色时,中间表记录不会自动删除。需要配置约束:
type User struct {
ID uint
Roles []Role `gorm:"many2many:user_roles;constraint:OnDelete:CASCADE;"`
}
多对多关系需要中间表,GORM 自动处理大部分细节。理解外键配置,掌握关联操作方法,能应对复杂业务场景。